# -*- coding: utf-8 -*-

import sys

import logging
import traceback

import numpy as np
import pandas as pd

from scipy.optimize import minimize

from util import get_r_squared
from db import get_data_by_proc

from flask import Blueprint
from flask import current_app, request

bp = Blueprint(name="rbsa", import_name=__name__, url_prefix="/rbsa")


@bp.route(rule='/', methods=['POST'])
def fund():
    current_app.logger.info("fund")
    request.json

    return ''


def get_columns(index_ids):
    """
    """
    columns = []
    i = 0
    indexs_list = index_ids.split('_')
    for item in indexs_list:
        columns.append(item + str(i))
        i += 1
    columns.insert(0, 'fundId')
    columns.insert(1, 'endDate')
    columns.insert(2, 'Rp')
    columns.append('Rf')
    columns.append('NN')
    return columns


def get_x0(fund_df, alpha_control, ignore_columns):
    x0 = []
    if alpha_control == 1:
        x0 = [1.0] * fund_df.index.size
        pass

    for column in list(fund_df.columns):
        if column not in ignore_columns:
            x0.append(fund_df[column])
            pass
        pass

    return x0


def get_p0(fund_df, alpha_control, ignore_columns):
    p0 = []
    if alpha_control == 1:
        p0.append(0)
        pass

    columns_length = fund_df.columns.size - 4
    for column in list(fund_df.columns):
        if column not in ignore_columns:
            p0.append(1.0 / float(columns_length))
            pass
        pass

    return tuple(p0)


def get_bounds(fund_df, bounds_type, bounds_list, alpha_control, ignore_columns):
    bounds = [(-float('inf'), float('inf'))]

    columns_length = fund_df.columns.size - 4

    if bounds_type == 0:
        bounds = bounds + bounds_list[0:columns_length]
        pass
    elif bounds_type > 0:
        for column in list(fund_df.columns):
            if column not in ignore_columns:
                if bounds_type == 1:  # 多头
                    bounds.append((0, 1))
                    pass
                elif bounds_type == 2:  # 多空
                    bounds.append((-columns_length, columns_length))
                    pass
                pass
            pass
        pass

    if bounds_type == 2:  # 多空
        bounds[-1] = (0, 1)
        pass

    if alpha_control == 0:
        bounds.pop(0)
        pass

    return tuple(bounds)


def get_constraints(beta, constraint_value: str, alpha_control):
    weight = constraint_value.split('_')

    i_list = []
    if alpha_control == 0:
        i_list = list(range(len(beta)))
        pass
    elif alpha_control == 1:
        i_list = list(range(1, len(beta)))
        pass
    pass

    # 1 - sum(weight_i * beta_i)
    sum = 0
    for i in i_list:
        if alpha_control == 0:
            sum += beta[i] * float(weight[i])
            pass
        elif alpha_control == 1:
            sum += beta[i] * float(weight[i - 1])
            pass
        pass

    return 1 - sum


def residual_squared_sum(beta, x0, y0):
    """残差平方和"""
    """Rp = sum(Wi * Ri)"""
    residual = y0 - (x0.T * np.array(beta)).sum(axis=1)
    return sum(residual ** 2)


def get_minimize(fund_df, p0, x0, y0, bounds_type, bounds_list, constraint_type, constraint_value,
                 alpha_control, ignore_columns):
    try:
        if constraint_type == 1:
            constraints = ({'type': 'eq', 'fun': get_constraints, 'args': (constraint_value, alpha_control)})
            pass
        else:
            constraints = ({'type': 'ineq', 'fun': get_constraints, 'args': (constraint_value, alpha_control)})
            pass

        bounds = get_bounds(fund_df, bounds_type, bounds_list, alpha_control, ignore_columns)
        result = minimize(residual_squared_sum, p0, args=(x0, y0), method='SLSQP', bounds=bounds,
                          constraints=constraints,
                          options={"maxiter": 200})
        if result['success']:
            return parse_result(result, x0, y0, alpha_control)
        else:
            return False
        pass
    except Exception as e:
        print(traceback.format_exc(e))
        return None


def get_roll_window(fund_df, fund_id, indexs_list, algorithm_control, alpha_control, window_length, roll_step,
                    bounds_type, bounds_list, constraint_type, constraint_value, style, strategy, curve_type,
                    result_columns, ignore_columns):
    x = np.array(get_x0(fund_df, alpha_control))
    x = pd.DataFrame(x).T

    result_df = pd.DataFrame([], index=[], columns=result_columns)

    times_index = 0
    # 倒叙滚动
    for i in range(len(fund_df.index), window_length - 1, -roll_step):
        x0 = np.array(x.iloc[i - window_length:i].T)
        y0 = np.array(fund_df.iloc[i - window_length:i]['Rp'])

        # 首次滚动
        if i > len(fund_df.index) - roll_step:
            p0 = get_p0(fund_df, alpha_control)
            pass
        else:
            p0 = result_df.iloc[times_index, 2:indexs_list.size + 2].tolist()
            if alpha_control == 1:
                alpha = result_df.iloc[times_index, -2]
                p0.insert(0, alpha)
                pass
            p0 = tuple(p0)
            times_index += 1
            pass

        data = get_minimize(fund_df, p0, x0, y0, bounds_type, bounds_list, constraint_type, constraint_value,
                            alpha_control, ignore_columns)
        end_date = fund_df.iloc[i - 1, 0].strftime("%Y-%m-%d")  # 截止日期
        if not data:  # 计算失败
            result_list = [fund_id, end_date]
            result_list.extend([np.nan] * (len(result_columns) - 2))
            pass
        else:

            alpha = 0
            if alpha_control == 1:
                alpha = data[-2]
                beta = data[0:-2]
                pass
            elif alpha_control == 0:
                beta = data[0:-1]
                pass

            result_list = beta

            # 灵敏度beta最大
            max_beta = 1 + max(0, beta)

            if alpha_control == 1:
                x0 = x.iloc[i - window_length: i].iloc[-1, 1:]
                pass
            elif alpha_control == 0:
                x0 = x.iloc[i - window_length: i].iloc[-1, :]
                pass

            # 归因attribution
            attribution = x0 * beta
            if alpha_control == 1:
                attribution['alpha'] = alpha
                pass
            # 残差epsilon
            attribution['epsilon'] = 0
            attribution[-1] = y0[-1] - sum(attribution) - alpha

            result_list.append(list(attribution))
            result_list.append(max_beta)
            if alpha_control == 1:
                result_list.append(alpha)
                pass
            r2 = data[-1]
            result_list.append(r2)

            result_list.insert(0, end_date)
            result_list.insert(0, fund_id)
            pass

        result_item = pd.DataFrame([result_list], index=[fund_id], columns=result_columns)
        result_df = pd.concat([result_df, result_item], axis=0)
        pass

    return result_df


def parse_result(result, x0, y0, alpha_control):
    try:
        """
     fun: 0.12467752335951668
     jac: array([ 0.00038779,  0.04338087, -0.00030978,  0.03523914, -0.00055611, 0.02256151, -0.00017076])
 message: 'Optimization terminated successfully'
    nfev: 57
     nit: 7
    njev: 7
  status: 0
 success: True
       x: array([2.48851294e-01, 1.11672824e-17, 3.86743924e-01, 0.00000000e+00, 7.17577185e-02, 0.00000000e+00, 2.92647064e-01])
        """
        result_length = result.x.size
        y_test = 0

        beta = {}
        i = 0
        if alpha_control == 1:
            alpha = result.x[0]
            y_test = alpha
            i = 1
            pass

        result_list = result.x[i:].tolist()
        if alpha_control == 1:
            result_list.append(alpha)
            pass

        while i < result_length:
            # 拟合函数求出超额收益和各行业指数收益的beta系数值
            beta[i] = float(result.x[i])
            # 贡献度=因子系数*区间累计收益率
            y_test += beta[i] * x0[i]

            i += 1
            pass

        if (alpha_control == 1 and np.isnan(beta[1])) \
                or (alpha_control == 0 and np.isnan(beta[0])):
            return None

        # 计算拟合优度
        y_test = np.array(y_test)
        y_true = y0
        r_squared = get_r_squared(y_true, y_test)
        result_list.append(r_squared)
        return result_list
    except Exception as e:
        print(traceback.format_exc(e))
        return None


def get_result_columns(index_list, algorithm_control, alpha_control):
    beta = []
    contribution = []
    for i in range(1, len(index_list) + 1):
        beta.append('beta%s' % i)
        contribution.append('contribution%s' % i)
        pass

    result_columns = ['fund_id', 'end_date', 'start_date'] + beta + contribution
    if algorithm_control == 0:
        if alpha_control == 1:
            result_columns += ['Alpha']  # 特殊含义
            pass
        pass

    result_columns += ['epsilon', 'miu', 'max_beta', 'alpha', 'r_squared']
    if alpha_control == 0:  # 删除倒数第二个alpha
        result_columns.pop(-2)
        pass
    return result_columns


def get_rbsa(fund_df, fund_id, indexs_list, algorithm_control, alpha_control, window_length, roll_step,
             bounds_type, bounds_list, constraint_type, constraint_value, style, strategy, curve_type,
             result_columns, ignore_columns):
    try:
        if algorithm_control == 0:
            p0 = get_p0(fund_df, alpha_control, ignore_columns)
            x0 = np.array(get_x0(fund_df, alpha_control, ignore_columns))
            y0 = np.array(fund_df['Rp'])

            data = get_minimize(fund_df, p0, x0, y0, bounds_type, bounds_list, constraint_type, constraint_value,
                                alpha_control, ignore_columns)

            start_date = fund_df.iloc[0, 0].strftime("%Y-%m-%d")  # 起始日期
            end_date = fund_df.iloc[-1, 0].strftime("%Y-%m-%d")  # 结束日期

            if not data:  # 计算失败
                result_list = [fund_id, end_date]
                result_list.extend([np.nan] * (len(result_columns) - 2))
                pass
            else:
                x0 = pd.DataFrame(x0).T
                if alpha_control == 1:
                    x0.drop(labels=['NN'], axis=0, inplace=True)
                    pass

                # 累计收益率
                cumulative_return = float((1 + y0).prod() - 1)
                if abs(cumulative_return) < sys.float_info.epsilon:
                    return None

                alpha = 0
                if alpha_control == 1:
                    alpha = data[-2]
                    beta = data[0:-2]
                    pass
                elif alpha_control == 0:
                    beta = data[0:-1]
                    pass

                result_list = beta
                # 归因attribution
                attribution = x0 * beta
                if alpha_control == 1:
                    attribution['alpha'] = alpha
                    pass
                attribution_sum = attribution.apply(lambda x: x.sum(), axis=1)
                # 残差epsilon
                attribution['epsilon'] = y0 - attribution_sum
                # 贡献度contribution
                contribution = (1 + attribution).prod() - 1
                # 累积交叉项损益miu
                miu = cumulative_return - sum(contribution)
                # 灵敏度beta最大
                max_beta = max(beta)
                if max_beta < 0:
                    max_beta = 0
                    pass
                max_beta += 1

                result_list.extend(contribution)
                result_list.append(miu)
                result_list.append(max_beta)
                if alpha_control == 1:
                    result_list.append(alpha)
                    pass
                r2 = data[-1]
                result_list.append(r2)

                result_list.insert(0, start_date)
                result_list.insert(0, end_date)
                result_list.insert(0, fund_id)
                pass

            result_df = pd.DataFrame([result_list], index=[fund_id], columns=result_columns)
            pass

        elif algorithm_control == 1:  # 滚动窗口
            result_df = get_roll_window(fund_df, fund_id, indexs_list, algorithm_control, alpha_control, window_length,
                                        roll_step, bounds_type, bounds_list, constraint_type, constraint_value, style,
                                        strategy, curve_type, result_columns)
            pass
        return result_df

    except Exception as e:
        print(traceback.format_exc(e))
        pass

    finally:

        pass

    pass


def run_rbsa() -> pd.DataFrame:
    fund_ids = 'MF00003PW1'
    index_ids = 'IN0000000S_IN0000000T_IN0000000W_IN0000000X_IN0000000U_IN0000000V_IN0000007G'

    funds_list = fund_ids.split('_')
    indexs_list = index_ids.split('_')

    start_date = '1990-01-01'
    end_date = '2022-02-28'

    data_type = 1
    return_length = None

    frequency = 'W'
    style = 'F1'

    strategy = 0
    curve_type = 7

    alpha_control = 0
    algorithm_control = 0  # 算法控制变量
    window_length = 24  # 滚动窗口长度
    roll_step = 24  # 窗口移动步长

    bounds_type = 1  # 上下限类型：0-使用传入的上下限参数，1-使用默认多头的上下限，2-使用默认空头的上下限
    up_bound_value = '_'.join('1' * len(indexs_list))  # 上限
    up_bounds_list = up_bound_value.split('_')
    down_bound_value = '_'.join('0' * len(indexs_list))  # 下限
    down_bounds_list = down_bound_value.split('_')
    bounds_list = []

    constraint_type = 1  # 约束条件：0-不等于，1-等于
    constraint_value = '_'.join('1' * len(indexs_list))   # 传入的约束条件的权重

    ignore_columns = ['endDate', 'Rp', 'NN', 'Rf']

    if bounds_type == 0:
        if len(indexs_list) != len(up_bounds_list):
            return None
        if len(indexs_list) != len(down_bounds_list):
            return None
        for i in range(len(indexs_list)):
            bounds_list.append((int(down_bounds_list[i]), int(up_bounds_list[i])))
            pass

        pass

    # 获取收益矩阵：基金，基准（指数）
    proc_name = 'sp_get_f_m_c_index_py'
    params = (fund_ids, index_ids, start_date, end_date, data_type, return_length, frequency, strategy, curve_type)
    result_columns = get_columns(index_ids)
    df = get_data_by_proc(proc_name=proc_name, params=params, columns=result_columns)
    if df is None or df.empty:  # 数据判空
        return None

    # 校验存储过程返回的数据格式的准确性
    # 除去默认返回的列：其他就是指数对应的列
    index_columns = set(df.columns) - set(get_columns(''))
    if len(index_columns) != len(indexs_list):
        return None

    # 返回数据
    result_columns = get_result_columns(indexs_list, algorithm_control, alpha_control)
    result = pd.DataFrame([], columns=result_columns)

    df['fundId'] = df['fundId'].astype(str)
    fund_id_list = df['fundId'].tolist()
    # 多个基金
    for fund_id in funds_list:
        if fund_id not in fund_id_list:  # 基金没查询到数据
            fund_result = pd.DataFrame([], index=[fund_id], columns=result_columns)
            fund_result.iloc[-1, 0] = fund_id
            result = pd.concat([result, fund_result], axis=0)
            pass
        # 单个基金的全部数据
        fund_df = df[df.fundId == fund_id]
        if (fund_df.index.size >= window_length and algorithm_control == 1) \
                or (fund_df.index.size >= len(indexs_list) + 4 and algorithm_control == 0):
            fund_df.drop(columns=['fundId'], inplace=True)  # 删除列
            fund_df.set_index(['endDate'], drop=False, inplace=True)  # 重新设置索引
            fund_rbsa = get_rbsa(fund_df, fund_id, indexs_list, algorithm_control, alpha_control, window_length,
                                 roll_step, bounds_type, bounds_list, constraint_type, constraint_value, style,
                                 strategy, curve_type, result_columns, ignore_columns)
            result = pd.concat([result, fund_rbsa], axis=0)
            pass
        else:  # 返回的数据不够计算
            fund_result = pd.DataFrame([], columns=result_columns, index=[fund_id])
            fund_result.iloc[-1, 0] = fund_id
            result = pd.concat([result, fund_result], axis=0)
            pass
        pass
    return result


import string


def resample():
    # 生成频率是月的时间序列
    n = 10  # 记录条数

    # start, end, periods, and freq, exactly three must be specified
    date_di = pd.date_range(start='2020-01-01', periods=n, freq='M')
    data_df = pd.DataFrame(data=np.arange(n * n).reshape(n, n), index=date_di,
                           columns=list(string.ascii_uppercase[:n]))

    re_df = data_df.resample(rule='3M', label='right')
    print(re_df)
    pass


if __name__ == '__main__':
    print(run_rbsa())
    # resample()
    pass
